/* ----------------------------------------------------------------------------
 * Copyright (c) Huawei Technologies Co., Ltd. 2013-2019. All rights reserved.
 * Description: implementation for telnet device.
 * Author: zhangyangyang
 * Create: 2013-01-01
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * --------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS OS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 * --------------------------------------------------------------------------- */

#include "los_config.h"
#ifdef LOSCFG_NET_TELNET
#include "sys/ioctl.h"
#include "sys/types.h"
#include "lwip/sockets.h"
#include "stdlib.h"
#include "hisoc/uart.h"
#include "lwip/opt.h"
#include "telnet_dev.h"
#include "telnet_loop.h"
#include "console.h"
#include "string.h"
#include "los_printf.h"
#if (LOSCFG_BASE_CORE_SWTMR == YES)
#include "los_swtmr_pri.h"
#endif

/* event: there are more commands left in the FIFO to run */
#define TELNET_EVENT_MORE_CMD 0x01
#define TELNET_DEV_DRV_MODE 0x666

static TELNET_DEV_S g_telnetDev;

/*
 * get telent device by filp
 */
static inline TELNET_DEV_S *GetTelnetDevByFile(struct file *file)
{
    struct inode *telnetInode = file->f_inode;
    TELNET_DEV_S *telnetDev = ((TELNET_DEV_S *)(telnetInode->i_private));
    return telnetDev;
}

/*
 * When receive user's input commands, first copy commands to the FIFO of the telnet device.
 * Then, notify a command resolver task (an individual shell task) to take out and run commands.
 *
 * Return values
 *  - -1 : On failure
 *  - Non-negative int: length of written commands
 */
int telnet_tx(char *buf, unsigned int bufLen)
{
    unsigned int i;
    unsigned int intSave;
    TELNET_DEV_S *telnetDev = &g_telnetDev;

    if (buf == NULL || telnetDev == NULL || telnetDev->cur_tel == NULL) {
        return -1;
    }

    intSave = LOS_IntLock();

    /* size limited */
    if (bufLen > telnetDev->cur_tel->fifo_num) {
        bufLen = telnetDev->cur_tel->fifo_num;
    }

    if (bufLen == 0) {
        LOS_IntRestore(intSave);
        return 0;
    }

    /* copy commands to the fifo of the telnet device */
    for (i = 0; i < bufLen; i++) {
        telnetDev->cur_tel->rx_buf[telnetDev->cur_tel->rx_index++] = *buf++;

        if (telnetDev->cur_tel->rx_index >= FIFO_MAX) {
            telnetDev->cur_tel->rx_index = 0;
        }
    }
    telnetDev->cur_tel->fifo_num -= bufLen;

    LOS_IntRestore(intSave);

    if (telnetDev->event_pend) {
        /* signal that there are some works to do */
        (void)LOS_EventWrite(&telnetDev->event_telnet, TELNET_EVENT_MORE_CMD);
    }
    /* notify the command resolver task */
    notify_poll(&telnetDev->wait);

    return bufLen;
}

/*
 * When open the telnet device, init the FIFO, wait queue etc.
 */
static int TelnetOpen(struct file *file)
{
    struct wait_queue_head *wait = NULL;
    TELNET_DEV_S *telnetDev = GetTelnetDevByFile(file);

    wait = &telnetDev->wait;
    if (telnetDev->cur_tel == NULL) {
        (void)LOS_EventInit(&telnetDev->event_telnet);
        telnetDev->cur_tel = (TELNTE_FIFO_S *)malloc(sizeof(TELNTE_FIFO_S));
        if (telnetDev->cur_tel == NULL) {
            return -1;
        }
        (void)memset_s(telnetDev->cur_tel, sizeof(TELNTE_FIFO_S), 0, sizeof(TELNTE_FIFO_S));
        telnetDev->cur_tel->fifo_num = FIFO_MAX;
        LOS_ListInit(&wait->poll_queue);
    }
    return 0;
}

/*
 * When close the telnet device, free the FIFO, wait queue etc.
 */
static int TelnetClose(struct file *file)
{
    struct wait_queue_head *wait = NULL;
    TELNET_DEV_S *telnetDev = GetTelnetDevByFile(file);

    wait = &telnetDev->wait;
    LOS_ListDelete(&wait->poll_queue);
    free(telnetDev->cur_tel);
    telnetDev->cur_tel = NULL;
    return 0;
}


/*
 * When a command resolver task trys to read the telnet device,
 * this method is called, and it will take out user's commands from the FIFO to run.
 *
 * Return values
 *  - -1 : On failure
 *  - Non-negative int: length of commands taken out from the FIFO of the telnet device.
 */
static ssize_t TelnetRead(struct file *file, char *buf, size_t bufLen)
{
    unsigned int i;
    unsigned int intSave;
    TELNET_DEV_S *telnetDev = GetTelnetDevByFile(file);

    if (buf == NULL || telnetDev == NULL || telnetDev->cur_tel == NULL) {
        return -1;
    }

    if (telnetDev->event_pend) {
        (void)LOS_EventRead(&telnetDev->event_telnet, TELNET_EVENT_MORE_CMD, LOS_WAITMODE_OR, LOS_WAIT_FOREVER);
    }

    intSave = LOS_IntLock();

    if (bufLen > (FIFO_MAX - telnetDev->cur_tel->fifo_num)) {
        bufLen = FIFO_MAX - telnetDev->cur_tel->fifo_num;
    }

    for (i = 0; i < bufLen; i++) {
        *buf++ = telnetDev->cur_tel->rx_buf[telnetDev->cur_tel->rx_out_index++];
        if (telnetDev->cur_tel->rx_out_index >= FIFO_MAX) {
            telnetDev->cur_tel->rx_out_index = 0;
        }
    }
    telnetDev->cur_tel->fifo_num += bufLen;
    /* check if no more commands left to run */
    if (telnetDev->cur_tel->fifo_num == FIFO_MAX) {
        (void)LOS_EventClear(&telnetDev->event_telnet, ~TELNET_EVENT_MORE_CMD);
    }

    LOS_IntRestore(intSave);
    return bufLen;
}

/*
 * When a command resolver task trys to write command results to the telnet device,
 * just use lwIP send function to send out results.
 */
static ssize_t TelnetWrite(struct file *file, const char *buf, size_t bufLen)
{
    int ret = 0;
    TELNET_DEV_S *telnetDev = GetTelnetDevByFile(file);

    if (telnetDev->cur_tel == NULL) {
        return -1;
    }

    if (OS_INT_ACTIVE) {
        return ret;
    }

    if (g_losTaskLock) {
        return ret;
    }

    if (telnetDev->clint_fd != 0) {
#if (LOSCFG_BASE_CORE_SWTMR == YES)
         /* DO NOT Call blocking API in software timer task */
        if (g_losTask.runTask->taskEntry == (TSK_ENTRY_FUNC)OsSwtmrTask) {
            return ret;
        }
#endif
        ret = send(telnetDev->clint_fd, buf, bufLen, 0);
    }
    return ret;
}

static int TelnetIoctl(struct file *file, int cmd, unsigned long arg)
{
    int ret = 0;
    TELNET_DEV_S *telnetDev = GetTelnetDevByFile(file);

    if (cmd == CFG_TELNET_EVENT_PEND) {
        if (arg == 0) {
            telnetDev->event_pend = false;
            (void)LOS_EventWrite(&(telnetDev->event_telnet), TELNET_EVENT_MORE_CMD);
            (void)LOS_EventClear(&(telnetDev->event_telnet), ~TELNET_EVENT_MORE_CMD);
        } else {
            telnetDev->event_pend = true;
        }
    } else if (cmd == CFG_TELNET_SET_FD) {
        if (arg >= FD_SETSIZE - 1) {
            return -1;
        }
        telnetDev->clint_fd = arg;
    }

    return ret;
}

static int TelnetPoll(struct file *file, poll_table *table)
{
    TELNET_DEV_S *telnetDev = GetTelnetDevByFile(file);

    if (telnetDev == NULL || telnetDev->cur_tel == NULL) {
        return -1;
    }

    poll_wait(file, &telnetDev->wait, table);

    /* check if there are some commands to run */
    if (telnetDev->cur_tel->fifo_num != FIFO_MAX) {
        return POLLIN | POLLRDNORM;
    }
    return 0;
}

static const struct file_operations_vfs g_telnetOps = {
    TelnetOpen,
    TelnetClose,
    TelnetRead,
    TelnetWrite,
    NULL,
    TelnetIoctl,
#ifndef CONFIG_DISABLE_POLL
    TelnetPoll,
#endif
    NULL
};

int telneted_unregister(void)
{
    free(g_telnetDev.cur_tel);
    g_telnetDev.cur_tel = NULL;
    (void)unregister_driver(TELNET);

    return 0;
}

int telneted_register(void)
{
    int ret;

    g_telnetDev.id = 0;
    g_telnetDev.cur_tel = NULL;
    g_telnetDev.event_pend = TRUE;

    ret = register_driver(TELNET, &g_telnetOps, TELNET_DEV_DRV_MODE, &g_telnetDev);
    if (ret != 0) {
        PRINT_ERR("Telnet register driver error.\n");
    }
    return ret;
}

int telnet_dev_init(int clientFd)
{
    int ret;
    ret = system_console_init(TELNET);
    if (ret != 0) {
        PRINT_ERR("Telnet console init error.\n");
    }
    (void)ioctl(STDIN, CFG_TELNET_SET_FD, clientFd);
    return ret;
}

int telnet_dev_deInit(void)
{
    int ret;
    ret = system_console_deinit(TELNET);
    if (ret != 0) {
        PRINT_ERR("Telnet console deinit error.\n");
    }
    return ret;
}
#endif

